package ua.pp.bizon.yandex.api;

import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public final class ObjectLookup {

	public static/* for tests final */String META_INF_SERVICES = "META-INF/services/";
	private static ObjectLookup lookup = new ObjectLookup();
	private static final Log log = LogFactory.getLog(ObjectLookup.class);

	private Map<String, Object> elements = new HashMap<String, Object>();

	/**
	 * this class lookup objects from META-INF/services/ + clazz.getName()
	 * property file
	 * 
	 * @param <T>
	 *            type for lookup class
	 * @param clazz
	 *            - class, that should be lookuped
	 * @return
	 */
	public static <T> T lookup(Class<T> clazz) {
		synchronized (lookup) {
			if (!lookup.elements.containsKey(clazz.getName())) {
				try {
					InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(META_INF_SERVICES + clazz.getName());
					Properties props = new Properties();
					props.load(inputStream);
					inputStream.close();
					String factoryClassName = props.getProperty(clazz.getName());
					Class<?> factoryClass = Class.forName(factoryClassName);
					if (props.containsKey(clazz.getName() + ".constructor.params.count")) {
						log.trace(clazz.getName() + " will be load with params");
						int count = Integer.parseInt(props.getProperty(clazz.getName() + ".constructor.params.count"));
						Class<?>[] parameterTypes = new Class<?>[count];
						Object[] parameterValues = new Object[count];
						for (int i = 0; i < count; i++) {
							String key = clazz.getName() + "." + i;
							parameterTypes[i] = Class.forName(props.getProperty(key + ".class", "java.lang.Object"));
							if (props.containsKey(key + ".value")) {
								parameterValues[i] = Class.forName(props.getProperty(key + ".value", "java.lang.Object")).newInstance();
							} else if (props.containsKey(key + ".lookup")) {
								parameterValues[i] = lookup(Class.forName(props.getProperty(key + ".lookup", "java.lang.Object")));
							} else if (props.containsKey(key + ".factory")) {
								parameterValues[i] = Class.forName(props.getProperty(key + ".factory", "java.lang.Object"))
										.getDeclaredField(props.getProperty(key + ".factoryMethod", "create")).get(null);
							}
						}
						log.trace("types: " + Arrays.asList(parameterTypes));
						log.trace("values: " + Arrays.asList(parameterValues));
						put(clazz, clazz.cast(factoryClass.getConstructor(parameterTypes).newInstance(parameterValues)));
					} else {
						put(clazz, clazz.cast(factoryClass.newInstance()));
					}
				} catch (Exception e) {
					log.error("error in " + clazz.getName() + ", caused by " + e.getMessage(), e);
				}
			}
		}
		Object obj = lookup.elements.get(clazz.getName());
		if (obj == null) {
			log.error("class " + clazz.getName() + " not found, may be NPE");
		}
		return clazz.cast(obj);
	}

	public static <T> void put(Class<T> clazz, T value) {
		log.trace("put: " + clazz.getName());
		lookup.elements.put(clazz.getName(), value);
	}
}
